/*!
    \copyright  Copyright (c) 2022 - 2024 Qualcomm Technologies International, Ltd.
                All Rights Reserved.
                Qualcomm Technologies International, Ltd. Confidential and Proprietary.
    \file
    \ingroup    gatt_server_gap
    \brief
*/

#include "gatt_server_gap_advertising.h"
#include "gatt_server_gap.h"
#include "gatt_server_gap_config.h"

#include "le_advertising_manager.h"
#include "local_name.h"

#include <logging.h>
#include <panic.h>
#include <stdlib.h>

#define GAP_ADVERT_FLAGS                           (BLE_FLAGS_GENERAL_DISCOVERABLE_MODE | BLE_FLAGS_DUAL_CONTROLLER | BLE_FLAGS_DUAL_HOST)
#define GAP_ADVERT_FLAGS_LENGTH                     3
#define GAP_APPEARANCE_ITEM_LENGTH                  4
#define GAP_APPEARANCE_ITEM_DATA_START_OCTET        2

typedef struct {
    le_adv_item_data_t item;
} gap_name_item_t;

static const uint8 gap_adv_flags_data[GAP_ADVERT_FLAGS_LENGTH] =
{
    GAP_ADVERT_FLAGS_LENGTH - 1,
    ble_ad_type_flags,
    GAP_ADVERT_FLAGS
};

static gap_name_item_t gap_name_item = {0};
static const le_adv_item_data_t gap_flags_item = {.data = gap_adv_flags_data, .size = sizeof(gap_adv_flags_data)};
static le_adv_item_data_t gap_appearance_item = {0};
static uint8 number_of_clients_using_local_name = 0;

/* Get the local device name and if needed truncate the size so it will fit into an advert. */
static const uint8 *gattServerGap_GetLocalName(uint16 *name_length, bool *truncated)
{
    uint16 name_len;
    bool was_truncated = FALSE;
    const uint8* name = LocalName_GetPrefixedName(&name_len);

    if (name_len > GAP_ADVERT_NAME_MAX_LENGTH)
    {
        /* If a client has specified that only the complete name should be
           advertised we cannot truncate the name. But we can't fit the name
           into an advert either. So Panic() - the device needs to use a
           shorter name. */
        PanicFalse(!GattServerGap_IsCompleteLocalNameBeingUsed());

        name_len = GAP_ADVERT_NAME_MAX_LENGTH;
        was_truncated = TRUE;
    }

    *name_length = name_len;
    *truncated = was_truncated;
    return name;
}

static unsigned gattServerGap_GetItemDataNameSize(void)
{
    uint16 length = 0;
    bool truncated = FALSE;
    gattServerGap_GetLocalName(&length, &truncated);
    return (unsigned)(length + AD_DATA_HEADER_SIZE);
}

static bool gattServerGap_GetItemDataName(le_adv_item_data_t * item)
{
    PanicNull(item);

    DEBUG_LOG("gattServerGap_GetAdvertisingItemName gap_name_item.item.data:%p", gap_name_item.item.data);

    uint16 name_len = 0;
    bool name_truncated = FALSE;
    const uint8* name = gattServerGap_GetLocalName(&name_len, &name_truncated);

    /* Check if name has changed, if it has free the current data to force it
       to be recreated with the new name. */
    if (gap_name_item.item.data)
    {
        if (memcmp(&gap_name_item.item.data[AD_DATA_HEADER_SIZE], name, gap_name_item.item.size))
        {
            free((void *)gap_name_item.item.data);
            gap_name_item.item.data = NULL;
        }
    }

    if (gap_name_item.item.data == NULL)
    {
        PanicNull((void*)name);

        uint16 data_len = gattServerGap_GetItemDataNameSize();
        uint8* data = PanicUnlessMalloc(data_len);

        data[AD_DATA_LENGTH_OFFSET] = name_len + 1;
        data[AD_DATA_TYPE_OFFSET] = name_truncated ? ble_ad_type_shortened_local_name : ble_ad_type_complete_local_name;
        memcpy(&data[AD_DATA_HEADER_SIZE], name, name_len);

        gap_name_item.item.size = data_len;
        gap_name_item.item.data = data;
    }
    number_of_clients_using_local_name++;
    *item = gap_name_item.item;
    return TRUE;
}

static void gattServerGap_ReleaseItemDataName(void)
{
    if(number_of_clients_using_local_name)
    {
        number_of_clients_using_local_name--;
        if (number_of_clients_using_local_name == 0 && gap_name_item.item.data)
        {
            free ((void *)gap_name_item.item.data);
            gap_name_item.item.data = NULL;
            gap_name_item.item.size = 0;
        }
    }
}

static bool gattServerGap_GetItemDataFlags(le_adv_item_data_t * data)
{
    PanicNull(data);
    *data = gap_flags_item;
    return TRUE;
}

static bool gattServerGap_GetItemDataAppearance(le_adv_item_data_t * data)
{
    PanicNull(data);
    uint8 *advert_data = PanicUnlessMalloc(GAP_APPEARANCE_ITEM_LENGTH);

    advert_data[0] = GAP_APPEARANCE_ITEM_LENGTH - 1;
    advert_data[1] = ble_ad_type_appearance;
    advert_data[GAP_APPEARANCE_ITEM_DATA_START_OCTET] = GattServerGap_GetAppearanceValue() & 0xFF;
    advert_data[GAP_APPEARANCE_ITEM_DATA_START_OCTET + 1] = GattServerGap_GetAppearanceValue() >> 8;

    gap_appearance_item.data = advert_data;
    gap_appearance_item.size = GAP_APPEARANCE_ITEM_LENGTH;
    *data = gap_appearance_item;

    return TRUE;
}

static void gattServerGap_ReleaseItemDataAppearance(void)
{
    if (gap_appearance_item.data)
    {
        free((void *)gap_appearance_item.data);
        gap_appearance_item.size = 0;
    }
}

static le_adv_item_handle gatt_server_gap_registered_item_name_handle = NULL;
static le_adv_item_handle gatt_server_gap_registered_item_name_legacy_handle = NULL;
static le_adv_item_handle gatt_server_gap_registered_item_flags_handle = NULL;
static le_adv_item_handle gatt_server_gap_registered_item_appearance_handle = NULL;

static bool gattServerGap_GetItemInfoFlags(le_adv_item_info_t * info)
{
    PanicNull(info);
    *info = (le_adv_item_info_t){ .placement = le_adv_item_data_placement_advert,
                                  .type = le_adv_type_legacy_connectable_scannable,
                                  .data_size = gap_flags_item.size };
    return TRUE;
}

static bool gattServerGap_GetItemInfoAppearance(le_adv_item_info_t * info)
{
    PanicNull(info);
    *info = (le_adv_item_info_t){ .placement = le_adv_item_data_placement_advert,
                                  .type =  le_adv_type_extended_connectable,
                                  .data_size = GAP_APPEARANCE_ITEM_LENGTH };
    return TRUE;
}

static le_adv_item_callback_t gattServerGap_AdvertisingManagerItemFlagsCallback =
{
    .GetItemData = &gattServerGap_GetItemDataFlags,
    .GetItemInfo = &gattServerGap_GetItemInfoFlags
};

static le_adv_item_callback_t gattServerGap_AdvertisingManagerItemAppearanceCallback =
{
    .GetItemData = &gattServerGap_GetItemDataAppearance,
    .ReleaseItemData = &gattServerGap_ReleaseItemDataAppearance,
    .GetItemInfo = &gattServerGap_GetItemInfoAppearance
};

static bool gattServerGap_GetItemDataNameAdvertising(le_adv_item_data_t * item)
{
    return gattServerGap_GetItemDataName(item);
}

static void gattServerGap_ReleaseItemDataNameAdvertising(void)
{
    gattServerGap_ReleaseItemDataName();
}

static bool gattServerGap_GetItemInfoNameAdvertising(le_adv_item_info_t * info)
{
    PanicNull(info);
    *info = (le_adv_item_info_t)
    {
        .placement = le_adv_item_data_placement_advert,
        .type = le_adv_type_extended_connectable,
        .data_size = gattServerGap_GetItemDataNameSize() 
    };
    return TRUE;
}

static le_adv_item_callback_t gattServerGap_AdvertisingManagerItemNameCallback =
{
    .GetItemData = &gattServerGap_GetItemDataNameAdvertising,
    .ReleaseItemData = &gattServerGap_ReleaseItemDataNameAdvertising,
    .GetItemInfo = &gattServerGap_GetItemInfoNameAdvertising
};

static bool gattServerGap_GetItemInfoNameLegacyAdvertising(le_adv_item_info_t * info)
{
    PanicNull(info);
    *info = (le_adv_item_info_t)
    {
        .placement = le_adv_item_data_placement_advert,
        .type = le_adv_type_legacy_connectable_scannable,
        .data_size = gattServerGap_GetItemDataNameSize() 
    };
    return TRUE;
}

static le_adv_item_callback_t gattServerGap_AdvertisingManagerItemNameLegacyCallback =
{
    .GetItemData = &gattServerGap_GetItemDataNameAdvertising,
    .ReleaseItemData = &gattServerGap_ReleaseItemDataNameAdvertising,
    .GetItemInfo = &gattServerGap_GetItemInfoNameLegacyAdvertising
};


bool GattServerGap_SetupLeAdvertisingData(void)
{
    DEBUG_LOG("GattServerGap_SetupLeAdvertisingData legacy=%d appearence=%d", GattServerGap_GetConfig()->legacy_device_support, GattServerGap_GetConfig()->include_appearance);
    if(!gatt_server_gap_registered_item_name_handle)
    {
        gatt_server_gap_registered_item_name_handle = LeAdvertisingManager_RegisterAdvertisingItemCallback(NULL, &gattServerGap_AdvertisingManagerItemNameCallback);
    }

    if((!!gatt_server_gap_registered_item_name_legacy_handle) != GattServerGap_GetConfig()->legacy_device_support)
    {
        if(!gatt_server_gap_registered_item_name_legacy_handle)
        {
            gatt_server_gap_registered_item_name_legacy_handle = LeAdvertisingManager_RegisterAdvertisingItemCallback(NULL, &gattServerGap_AdvertisingManagerItemNameLegacyCallback);
        }
        else
        {
            LeAdvertisingManager_UnregisterAdvertisingItem(gatt_server_gap_registered_item_name_legacy_handle);
            gatt_server_gap_registered_item_name_legacy_handle = NULL;
        }
    }

    if(!gatt_server_gap_registered_item_flags_handle)
    {
        gatt_server_gap_registered_item_flags_handle = LeAdvertisingManager_RegisterAdvertisingFlagsCallback(NULL, &gattServerGap_AdvertisingManagerItemFlagsCallback);
    }

    if((!!gatt_server_gap_registered_item_appearance_handle) != GattServerGap_GetConfig()->include_appearance)
    {
        if(!gatt_server_gap_registered_item_appearance_handle)
        {
            gatt_server_gap_registered_item_appearance_handle = LeAdvertisingManager_RegisterAdvertisingItemCallback(NULL, &gattServerGap_AdvertisingManagerItemAppearanceCallback);
        }
        else
        {
            LeAdvertisingManager_UnregisterAdvertisingItem(gatt_server_gap_registered_item_appearance_handle);
            gatt_server_gap_registered_item_appearance_handle = NULL;
        }
    }
    return ((gatt_server_gap_registered_item_name_handle
            && gatt_server_gap_registered_item_flags_handle
            && ((!!gatt_server_gap_registered_item_name_legacy_handle) == GattServerGap_GetConfig()->legacy_device_support)
            && ((!!gatt_server_gap_registered_item_appearance_handle) == GattServerGap_GetConfig()->include_appearance)
            ) ? TRUE : FALSE);
}

bool GattServerGap_UpdateLeAdvertisingData(void)
{
    return ((gatt_server_gap_registered_item_name_handle ? LeAdvertisingManager_UpdateAdvertisingItem(gatt_server_gap_registered_item_name_handle) : FALSE)
            && (gatt_server_gap_registered_item_name_legacy_handle ? LeAdvertisingManager_UpdateAdvertisingItem(gatt_server_gap_registered_item_name_legacy_handle) : TRUE)
            && (gatt_server_gap_registered_item_flags_handle ? LeAdvertisingManager_UpdateAdvertisingItem(gatt_server_gap_registered_item_flags_handle) : FALSE)
            && (gatt_server_gap_registered_item_appearance_handle ? LeAdvertisingManager_UpdateAdvertisingItem(gatt_server_gap_registered_item_appearance_handle) : TRUE)
            );
}

void GattServerGap_DisableAdvertising(void)
{
    if(gatt_server_gap_registered_item_name_handle)
    {
        LeAdvertisingManager_UnregisterAdvertisingItem(gatt_server_gap_registered_item_name_handle);
        gatt_server_gap_registered_item_name_handle = NULL;
    }
    if(gatt_server_gap_registered_item_name_legacy_handle)
    {
        LeAdvertisingManager_UnregisterAdvertisingItem(gatt_server_gap_registered_item_name_legacy_handle);
        gatt_server_gap_registered_item_name_legacy_handle = NULL;
    }
    if(gatt_server_gap_registered_item_flags_handle)
    {
        LeAdvertisingManager_UnregisterAdvertisingItem(gatt_server_gap_registered_item_name_legacy_handle);
        gatt_server_gap_registered_item_flags_handle = NULL;
    }
    if(gatt_server_gap_registered_item_appearance_handle)
    {
        LeAdvertisingManager_UnregisterAdvertisingItem(gatt_server_gap_registered_item_name_legacy_handle);
        gatt_server_gap_registered_item_appearance_handle = NULL;
    }
}
